/********************************************************************************/
/*										*/
/*		JiveRunTime.java						*/
/*										*/
/*	Run time entries for JIVE visualization 				*/
/*										*/
/********************************************************************************/
/*	Copyright 2002 Brown University -- Steven P. Reiss		      */
/*********************************************************************************
 *  Copyright 2002, Brown University, Providence, RI.				 *
 *										 *
 *			  All Rights Reserved					 *
 *										 *
 *  Permission to use, copy, modify, and distribute this software and its	 *
 *  documentation for any purpose other than its incorporation into a		 *
 *  commercial product is hereby granted without fee, provided that the 	 *
 *  above copyright notice appear in all copies and that both that		 *
 *  copyright notice and this permission notice appear in supporting		 *
 *  documentation, and that the name of Brown University not be used in 	 *
 *  advertising or publicity pertaining to distribution of the software 	 *
 *  without specific, written prior permission. 				 *
 *										 *
 *  BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS		 *
 *  SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND		 *
 *  FITNESS FOR ANY PARTICULAR PURPOSE.  IN NO EVENT SHALL BROWN UNIVERSITY	 *
 *  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 	 *
 *  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,		 *
 *  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS		 *
 *  ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 	 *
 *  OF THIS SOFTWARE.								 *
 *										 *
 ********************************************************************************/

/* RCS: $Header: /pro/spr_cvs/pro/bloom/javasrc/edu/brown/bloom/jive/JiveRunTime.java,v 1.8 2003-05-24 00:25:01 spr Exp $ */


/*********************************************************************************
 *
 * $Log: JiveRunTime.java,v $
 * Revision 1.8  2003-05-24 00:25:01  spr
 * Buffer files to improve performance.
 *
 * Revision 1.7  2003/05/16 21:01:48  spr
 * Bug fixes from demo; ensure we can detail the library classes.
 *
 * Revision 1.6  2003/04/24 20:08:37  spr
 * Handle native methods that are triggers by using a stub; handle AWT-Motif thread.
 *
 * Revision 1.5  2003/02/06 20:40:11  spr
 * Fix box display overwriting lines; add lock-on statistics for threads.
 *
 * Revision 1.4  2002/11/15 20:38:59  spr
 * Fix problems with multiple runs.  Add brightness and tie to synchronizations.
 *
 * Revision 1.3  2002/11/01 21:02:17  spr
 * Complete the jive implementation with output for threads and classes.
 *
 * Revision 1.2  2002/10/26 14:56:29  spr
 * Output setup information listing classes at startup.
 *
 * Revision 1.1  2002/10/24 20:22:07  spr
 * Initial version of the JIVE java interactive viewing tool.  This version only
 * includes the code to patch and generate periodic information about a running
 * program.
 *
 *
 ********************************************************************************/


package edu.brown.bloom.jive;


import org.w3c.dom.*;
import javax.xml.parsers.*;


import java.util.*;
import java.io.*;
import java.net.*;
import java.awt.*;



public class JiveRunTime implements JiveConstants
{




/********************************************************************************/
/*										*/
/*	Local Storage								*/
/*										*/
/********************************************************************************/

private static JiveRunTime		the_runtime = null;
private static Object			runtime_mutex = null;

private ItemTable	item_table;

private int		thread_counter;

private JiveTimer	jive_timer;

private int []		index_map;
private int []		code_map;
private int		base_index;

private ThreadState []	thread_table;

private Stats		current_stats;
private Stats		eval_stats;
private Stats		next_stats;

private Client		out_client;
private XmlWriter	xml_out;
private String		process_id;

private Map		class_map;

private boolean 	is_apple;

private static final int	SAVE_DEPTH = 64;




/********************************************************************************/
/*										*/
/*	Top level entry points							*/
/*										*/
/********************************************************************************/

public static void handleStart()
{
   the_runtime = new JiveRunTime();
}



public static void handleEnter(int mid)
{
   if (the_runtime != null) the_runtime.processEnter(mid);
}


public static void handleExit(int mid)
{
   if (the_runtime != null) the_runtime.processExit(mid);
}



public static void handleAlloc(int mid,int cid)
{
   if (the_runtime != null) the_runtime.processAlloc(mid,cid);
}




public static void handleLock(Object mon)
{
   if (the_runtime != null) the_runtime.processLock(mon,0);
}



public static void handleLockEnter(Object mon)
{
   if (the_runtime != null) the_runtime.processLock(mon,1);
}



public static void handleLockExit(Object mon)
{
   if (the_runtime != null) the_runtime.processLock(mon,2);
}




/********************************************************************************/
/*										*/
/*	Constructors								*/
/*										*/
/********************************************************************************/

JiveRunTime()
{
   String master = null;
   String sockid = null;

   String xmlfile = System.getProperty(JIVE_PROPERTY_XML_FILE);
   process_id = System.getProperty(JIVE_PROPERTY_PID);
   if (process_id == null) process_id = "ANY";
   String vendor = System.getProperty("java.vendor");
   is_apple = vendor.startsWith("Apple");

   sockid = System.getProperty(JIVE_PROPERTY_SOCKET_ID);
   master = System.getProperty(JIVE_PROPERTY_MASTER);

   item_table = new ItemTable();
   index_map = null;
   code_map = null;
   class_map = new HashMap();
   thread_counter = 0;
   thread_table = new ThreadState[JIVE_MAX_THREADS];
   for (int i = 0; i < JIVE_MAX_THREADS; ++i) thread_table[i] = null;

   try {
      FileInputStream fr = new FileInputStream(xmlfile);
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setValidating(false);
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document d = db.parse(fr);
      fr.close();
      Node n = d.getDocumentElement();
      setupXmlData(n);
      item_table.setup();
      loadXmlData(n);
      File f = new File(xmlfile);
    }
   catch (Exception e) {
      System.err.println("JIVE: Problem parsing xml file: " + e.getMessage());
      e.printStackTrace();
    }

   setupIndexMap();
   current_stats = new Stats();
   eval_stats = new Stats();
   next_stats = new Stats();

   current_stats.setTime(System.currentTimeMillis());

   if (sockid != null) {
      out_client = new SocketClient(sockid);
    }
   else if (master != null) {
      out_client = new MintClient(master);
    }
   else {
      out_client = new FileClient();
    }
   xml_out = out_client.getXmlWriter();

   outputSetup();

   jive_timer = new JiveTimer();
   jive_timer.start();
}



/********************************************************************************/
/*										*/
/*	Processing methods							*/
/*										*/
/********************************************************************************/

private void processEnter(int mid)
{
   int idx = index_map[mid-base_index];
   if (idx != 0) {
      current_stats.item_counts[idx]++;
    }

   int code = code_map[mid-base_index];
   if (code != 0) {
      switch (code) {
	 case JIVE_CODE_NORMAL :
	    break;
	 case JIVE_CODE_WAIT :
	    setState(JIVE_STATE_WAIT,null);
	    break;
	 case JIVE_CODE_SLEEP :
	    setState(JIVE_STATE_SLEEP,null);
	    break;
	 case JIVE_CODE_IO :
	    setState(JIVE_STATE_IO,null);
	    break;
	 case JIVE_CODE_THREAD :
	    setState(JIVE_STATE_RUN,null);
	    break;
       }
    }
}



private void processExit(int mid)
{
   int code = code_map[mid-base_index];

   if (code != 0) {
      switch (code) {
	 case JIVE_CODE_NORMAL :
	    break;
	 case JIVE_CODE_WAIT :
	 case JIVE_CODE_SLEEP :
	 case JIVE_CODE_IO :
	    setState(JIVE_STATE_REVERT,null);
	    break;
	 case JIVE_CODE_THREAD :
	    setState(JIVE_STATE_DEAD,null);
	    break;
       }
    }

}



private void processAlloc(int mid,int cid)
{
   int idx = index_map[mid-base_index];
   if (idx != 0) {
      current_stats.alloc_by[idx]++;
    }

   if (cid != 0) {
      idx = index_map[cid-base_index];
      if (idx != 0) {
	 current_stats.alloc_of[idx]++;
       }
    }
}



private void processLock(Object o,int state)
{
   switch (state) {
      case 0 :			// lock
	 setState(JIVE_STATE_BLOCK,o);
	 if (o != null) {
	    String cnm = o.getClass().getName();
	    RtClass rc = (RtClass) class_map.get(cnm);
	    if (rc != null) {
	       int idx = index_map[rc.getIndex()-base_index];
	       if (idx > 0) current_stats.block_count[idx]++;
	     }
	  }
	 break;
      case 1 :
	 setState(JIVE_STATE_SYNC,o);
	 break;
      case 2 :
	 setState(JIVE_STATE_REVERT,o);
	 break;
    }
}




/********************************************************************************/
/*										*/
/*	Thread state management methods 					*/
/*										*/
/********************************************************************************/

private void setState(int state,Object o)
{
   Thread t = Thread.currentThread();
   int thidx = t.__jive_field__;

   if (thidx == 0) {
      synchronized(this) {
	 thidx = ++thread_counter;
	 t.__jive_field__ = thidx;
	 thread_table[thidx] = new ThreadState(thidx,t);
	 if (t.getName().equals("AWT-Motif")) state = JIVE_STATE_IO;
	 else if (is_apple && t.getName().equals("Thread-2")) state = JIVE_STATE_WAIT;
       }
    }
   else if (thidx < 0) return;

   thread_table[thidx].setState(state,o);
}




private class ThreadState {

   private Thread for_thread;
   private int current_state;
   private long start_time;
   private int thread_index;
   private Object lock_object;
   private int [] prior_state;
   private Object [] prior_object;
   private int prior_ct;

   ThreadState(int idx,Thread t) {
      for_thread = t;
      if (t.getName().equals("Finalizer")) current_state = JIVE_STATE_SLEEP;
      else if (t.getName().equals("AWT-Motif")) current_state = JIVE_STATE_IO;
      else if (is_apple && t.getName().equals("Thread-2")) current_state = JIVE_STATE_WAIT;
      else current_state = JIVE_STATE_RUN;
      prior_state = new int[SAVE_DEPTH];
      prior_object = new Object[SAVE_DEPTH];
      prior_ct = 0;
      pushState();
      start_time = System.currentTimeMillis();
      thread_index = idx;
      lock_object = null;
    }

   void setState(int state,Object o) {
      switch (state) {
	 case JIVE_STATE_BLOCK :
	 case JIVE_STATE_IO :
	 case JIVE_STATE_SLEEP :
	 case JIVE_STATE_WAIT :
	    pushState();
	    break;
	 case JIVE_STATE_REVERT :
	    if (o != null && o == lock_object) lock_object = null;
	    state = popState();
	    lock_object = topObject();
	    break;
       }

      if (state == JIVE_STATE_BLOCK && o != null) {
	 for (int i = 0; i < thread_counter; ++i) {
	    ThreadState ts = thread_table[i];
	    if (ts == null || ts == this) continue;
	    boolean fg = false;
	    if (ts.lock_object == o && ts.current_state == JIVE_STATE_SYNC) {
	       fg = true;
	     }
	    else {
	       for (int j = 0; j < ts.prior_ct; ++j) {
		  if (ts.prior_object[j] == o && ts.prior_state[j] == JIVE_STATE_SYNC) {
		     fg = true;
		     break;
		   }
		}
	     }
	    if (fg) {
	       current_stats.addThreadLock(i);
	       break;
	     }
	  }
       }

      if (state == JIVE_STATE_RUN) {
	 if (lock_object != null) state = JIVE_STATE_SYNC;
	 o = null;
       }

      long time = System.currentTimeMillis();

      if (state == current_state) return;

      current_stats.setThread(thread_index,current_state,start_time,time,state);
      current_state = state;
      start_time = time;
      if (state == JIVE_STATE_BLOCK) lock_object = o;
    }

   void outputFields() {
      xml_out.field("INDEX",thread_index);
      xml_out.field("NAME",for_thread.getName());
    }

   boolean isSynced()			{ return lock_object != null; }

   private void pushState() {
      prior_object[prior_ct] = lock_object;
      prior_state[prior_ct++] = current_state;
      if (prior_ct >= SAVE_DEPTH) --prior_ct;
    }
   private int popState() {
      if (prior_ct > 0) --prior_ct;
      return prior_state[prior_ct];
    }
   private Object topObject() {
      if (prior_ct == 0) return null;
      return prior_object[prior_ct-1];
    }

}	// end of ThreadState




/********************************************************************************/
/*										*/
/*	Statistics output methods						*/
/*										*/
/********************************************************************************/

private void outputSetup()
{
   out_client.sendHeader();

   xml_out.begin("JIVERUN");
   xml_out.field("PID",process_id);
   xml_out.begin("DATA");
   xml_out.field("TIME",System.currentTimeMillis());
   for (int i = 1; i < index_map.length; ++i) {
      if (index_map[i] == i) {
	 RtItem ri = item_table.getItem(i+base_index);
	 if (ri != null) ri.outputItem(xml_out);
       }
    }
   xml_out.end();
   xml_out.end();

   out_client.sendTrailer();
}




private void collectStatistics()
{
   long time  = System.currentTimeMillis();

   next_stats.reset();

   Stats x = eval_stats;
   eval_stats = current_stats;
   current_stats = next_stats;
   next_stats = x;
   current_stats.setTime(time);

   out_client.sendHeader();

   xml_out.begin("JIVERUN");
   xml_out.field("PID",process_id);
   xml_out.begin("STATS");
   xml_out.field("TIME",time);

   int tot0 = 0;
   int tot1 = 0;
   int tot2 = 0;
   int tot3 = 0;
   for (int i = 0; i < eval_stats.item_counts.length; ++i) {
      int i0 = eval_stats.item_counts[i];
      int i1 = eval_stats.alloc_of[i];
      int i2 = eval_stats.alloc_by[i];
      int i3 = eval_stats.block_count[i];

      if (i0 + i1 + i2 + i3 == 0) continue;

      xml_out.begin("ENTRY");
      RtItem ri = item_table.getItem(i+base_index);
      xml_out.field("NAME",ri.getName());
      if (i0 != 0) xml_out.field("COUNT",i0);
      if (i1 != 0) xml_out.field("AOF",i1);
      if (i2 != 0) xml_out.field("ABY",i2);
      if (i3 != 0) xml_out.field("BLOCK",i3);
      xml_out.end();

      tot0 += i0;
      tot1 += i1;
      tot2 += i2;
      tot3 += i3;
    }

   xml_out.begin("TOTALS");
   xml_out.field("COUNT",tot0);
   xml_out.field("AOF",tot1);
   xml_out.field("ABY",tot2);
   if (tot3 != 0) xml_out.field("BLOCK",tot3);
   xml_out.end();

   eval_stats.fixThreads(next_stats,time);
   eval_stats.outputThreads();

   xml_out.end();
   xml_out.end();

   out_client.sendTrailer();
}




/********************************************************************************/
/*										*/
/*	Methods to load XML data from patcher					*/
/*										*/
/********************************************************************************/

private void setupXmlData(Node xml)
{
   if (xml.getNodeName().equals("JIVE")) {
      for (Node n = xml.getFirstChild(); n != null; n = n.getNextSibling()) {
	 if (n.getNodeType() == Node.ELEMENT_NODE) setupXmlData(n);
       }
    }
   else if (xml.getNodeName().equals("METHOD")) {
      setupItem(xml);
    }
   else if (xml.getNodeName().equals("CLASS")) {
      setupItem(xml);
    }
   else if (xml.getNodeName().equals("PACKAGE")) {
      setupItem(xml);
    }
}



private void setupItem(Node xml)
{
   int idx = getAttrInt(xml,"INDEX");
   item_table.noteIndex(idx);
}




private void loadXmlData(Node xml)
{
   if (xml.getNodeName().equals("JIVE")) {
      for (Node n = xml.getFirstChild(); n != null; n = n.getNextSibling()) {
	 if (n.getNodeType() == Node.ELEMENT_NODE) loadXmlData(n);
       }
    }
   else if (xml.getNodeName().equals("METHOD")) {
      loadMethod(xml);
    }
   else if (xml.getNodeName().equals("CLASS")) {
      loadClass(xml);
    }
   else if (xml.getNodeName().equals("PACKAGE")) {
      loadPackage(xml);
    }
}


private void loadMethod(Node xml)
{
   String nm = getAttrString(xml,"NAME");
   String sign = getAttrString(xml,"SIGNATURE");
   int sln = getAttrInt(xml,"START");
   int eln = getAttrInt(xml,"END");
   int idx = getAttrInt(xml,"INDEX");
   String file = getAttrString(xml,"FILE");
   int code = getAttrInt(xml,"CODE");
   int map = getAttrInt(xml,"MAP");

   RtMethod rm = new RtMethod(idx,nm,sign,sln,eln,file,code,map);
   item_table.setElement(idx,rm);
}



private void loadClass(Node xml)
{
   String nm = getAttrString(xml,"NAME");
   int idx = getAttrInt(xml,"INDEX");
   String fl = getAttrString(xml,"FILE");
   int map = getAttrInt(xml,"MAP");
   RtClass rc = new RtClass(idx,nm,fl,map);
   item_table.setElement(idx,rc);
   class_map.put(nm,rc);
}




private void loadPackage(Node xml)
{
   String nm = getAttrString(xml,"NAME");
   int idx = getAttrInt(xml,"INDEX");
   RtPackage rp = new RtPackage(idx,nm);
   item_table.setElement(idx,rp);
}




private String getAttrString(Node xml,String id)
{
   if (xml != null && xml instanceof Element) {
      Element e = (Element) xml;
      return e.getAttribute(id);
    }

   return null;
}



private int getAttrInt(Node xml,String id)
{
   if (xml != null && xml instanceof Element) {
      Element e = (Element) xml;
      String s = e.getAttribute(id);
      try {
	 return Integer.parseInt(s);
       }
      catch (NumberFormatException ex) { }
    }

   return -1;
}




/********************************************************************************/
/*										*/
/*	Methods to take care of mapping 					*/
/*										*/
/********************************************************************************/

private void setupIndexMap()
{
   int bid = item_table.getBaseId();
   int mid = item_table.getMaxId();

   index_map = new int[mid-bid+2];
   code_map = new int[mid-bid+2];
   base_index = bid-1;

   index_map[0] = 0;

   for (int i = bid; i <= mid; ++i) {
      RtMethod rm = item_table.getMethod(i);
      if (rm != null) {
	 code_map[i-base_index] = rm.getCode();
	 index_map[i-base_index] = rm.getMap()-base_index;
       }
      else {
	 code_map[i-base_index] = JIVE_CODE_NORMAL;
	 RtClass rc = item_table.getClass(i);
	 if (rc != null) index_map[i-base_index] = rc.getMap()-base_index;
	 else index_map[i-base_index] = 0;
       }
      int j = index_map[i-base_index];
      if (j <= 0) index_map[i-base_index] = 0;
      else if (index_map[j] == 0) index_map[j] = j;
    }
}




/********************************************************************************/
/*										*/
/*	Class to hold the index table						*/
/*										*/
/********************************************************************************/

private static class ItemTable {

   private RtItem [] the_table;
   private int base_id;
   private int max_id;

   ItemTable() {
      base_id = -1;
      max_id = 0;
      the_table = null;
    }

   void noteIndex(int idx) {
      if (base_id < 0 || idx < base_id) base_id = idx;
      if (idx > max_id) max_id = idx;
    }

   void setup() {
      the_table = new RtItem[max_id-base_id+1];
      for (int i = 0; i < the_table.length; ++i) the_table[i] = null;
    }

   void setElement(int idx,RtItem itm)		{ the_table[idx-base_id] = itm; }

   RtItem getItem(int idx) {
      return the_table[idx-base_id];
    }

   RtMethod getMethod(int idx) {
      RtItem itm = getItem(idx);
      if (itm != null && itm instanceof RtMethod) return (RtMethod) itm;
      return null;
    }

   RtClass getClass(int idx) {
      RtItem itm = getItem(idx);
      if (itm != null && itm instanceof RtClass) return (RtClass) itm;
      return null;
    }

   RtPackage getPackage(int idx) {
      RtItem itm = getItem(idx);
      if (itm != null && itm instanceof RtPackage) return (RtPackage) itm;
      return null;
    }

   int getBaseId()				{ return base_id; }
   int getMaxId()				{ return max_id; }
}




/********************************************************************************/
/*										*/
/*	Top level class for method or block					*/
/*										*/
/********************************************************************************/

private abstract static class RtItem {

   private int item_index;
   private String item_name;

   protected RtItem(int idx,String nm) {
      item_index = idx;
      item_name = nm;
    }

   int getIndex()				{ return item_index; }
   String getName()				{ return item_name; }

   abstract void outputItem(XmlWriter xw);

}	// end of subclass RtItem



/********************************************************************************/
/*										*/
/*	Class representing a method						*/
/*										*/
/********************************************************************************/

private static class RtMethod extends RtItem {

   private String method_sign;
   private int start_line;
   private int end_line;
   private String method_file;
   private int method_code;
   private int method_map;

   RtMethod(int idx,String nm,String sg,int sln,int eln,String fil,int cd,int map) {
      super(idx,nm);
      method_sign = sg;
      start_line = sln;
      end_line = eln;
      method_file = fil;
      method_code = cd;
      method_map = map;
    }

   int getMap() 				{ return method_map; }
   int getCode()				{ return method_code; }

   void outputItem(XmlWriter xw) {
      xw.begin("METHOD");
      xw.field("NAME",getName());
      if (start_line > 0) {
	 xw.field("START",start_line);
	 xw.field("END",end_line);
       }
      if (method_file != null) xw.field("FILE",method_file);
      xw.end();
    }

}	// end of subclass RtMethod





/********************************************************************************/
/*										*/
/*	Class representing a class						*/
/*										*/
/********************************************************************************/

private static class RtClass extends RtItem {

   private String class_file;
   private int class_map;

   RtClass(int idx,String nm,String fl,int mp) {
      super(idx,nm);
      class_file = fl;
      class_map = mp;
    }

   int getMap() 				{ return class_map; }

   void outputItem(XmlWriter xw) {
      xw.begin("CLASS");
      xw.field("NAME",getName());
      xw.end();
    }

}	// end of subclass RtClass





/********************************************************************************/
/*										*/
/*	Class representing a package						*/
/*										*/
/********************************************************************************/

private static class RtPackage extends RtItem {

   RtPackage(int idx,String nm) {
      super(idx,nm);
    }

   void outputItem(XmlWriter xw) {
      xw.begin("PACKAGE");
      xw.field("NAME",getName());
      xw.end();
    }

}	// end of subclass RtPackage





/********************************************************************************/
/*										*/
/*	Class for statistical data						*/
/*										*/
/********************************************************************************/

private class Stats {

   public int [] item_counts;
   public int [] alloc_of;
   public int [] alloc_by;
   public int [] block_count;
   public ThreadStats [] thread_data;
   public long stat_time;
   public long interval_time;

   Stats() {
      int bid = item_table.getBaseId();
      int mid = item_table.getMaxId();

      item_counts = new int [mid-bid+1];
      alloc_of = new int [mid-bid+1];
      alloc_by = new int [mid-bid+1];
      block_count = new int [mid-bid+1];
      thread_data = new ThreadStats[64];
      for (int i = 0; i < thread_data.length; ++i) thread_data[i] = new ThreadStats();
      reset();
      stat_time = 0;
    }

   void setTime(long t) 			{ stat_time = t; }

   public void reset() {
      int ln = item_counts.length;
      for (int i = 0; i < ln; ++i) {
	 item_counts[i] = 0;
	 alloc_of[i] = 0;
	 alloc_by[i] = 0;
	 block_count[i] = 0;
       }
      int mxt = thread_counter+1;
      if (thread_data.length < mxt) {
	 ThreadStats [] od = thread_data;
	 thread_data = new ThreadStats[od.length*2];
	 for (int i = 0; i < od.length; ++i) thread_data[i] = od[i];
	 for (int i = od.length; i < thread_data.length; ++i) thread_data[i] = new ThreadStats();
       }
      for (int i = 1; i < mxt; ++i) {
	 ThreadState th = thread_table[i];
	 if (th == null) break;
	 thread_data[i].reset();
       }
    }

   public void setThread(int idx,int state,long start,long time,int nstate) {
      if (idx >= thread_data.length) return;
      if (start < stat_time) start = stat_time;
      thread_data[idx].set(state,time-start,time,nstate);
    }

   public void addThreadLock(int idx) {
      if (idx >= thread_data.length) return;
      thread_data[idx].addBlock();
    }

   public void fixThreads(Stats prior,long until) {
      for (int i = 1; i < thread_data.length; ++i) {
	 ThreadStats ts = thread_data[i];
	 if (ts.last_state == JIVE_STATE_NEW) break;
	 if (ts.last_state == JIVE_STATE_STABLE) {
	    ts.updateFrom(prior.thread_data[i],i);
	  }
	 ts.finishInterval(stat_time,until);
	 interval_time = until-stat_time;
       }
    }

   public void outputThreads() {
      double [] totals = new double[JIVE_STATE_MAX];
      int blocks = 0;
      for (int i = 1; i < thread_data.length; ++i) {
	 ThreadStats ts = thread_data[i];
	 if (ts.last_state == JIVE_STATE_NEW) break;
	 ts.updateTotals(totals);
	 blocks += ts.getBlocks();
	 xml_out.begin("THREAD");
	 thread_table[i].outputFields();
	 ts.output();
	 xml_out.end();
       }
      xml_out.begin("THTOTAL");
      xml_out.field("ITIME",interval_time);
      for (int i = 0; i < JIVE_STATE_MAX; ++i) {
	 if (totals[i] != 0) {
	    xml_out.field(state_names[i],(long) totals[i]);
	  }
       }
      if (blocks != 0) xml_out.field("BLOCKS",blocks);
      xml_out.end();
    }

}	// end of subclass Stats




private class ThreadStats {

   public double [] state_times;
   public long last_time;
   public int last_state;
   public int block_ons;

   ThreadStats() {
      state_times = new double[JIVE_STATE_MAX];
      last_time = 0;
      last_state = JIVE_STATE_NEW;
      block_ons = 0;
    }

   void reset() {
      for (int i = 0; i < JIVE_STATE_MAX; ++i) state_times[i] = 0;
      last_state = JIVE_STATE_STABLE;
      last_time = 0;
      block_ons = 0;
    }

   void set(int state,long delta,long time,int nstate) {
      if (delta == 0) state_times[state] += JIVE_ZERO_TIME;
      else state_times[state] += delta;
      last_time = time;
      last_state = nstate;
    }

   void finishInterval(long start,long until) {
      if (last_time > start) start = last_time;
      state_times[last_state] += until-start;
    }

   void output() {
      for (int i = 0; i < JIVE_STATE_MAX; ++i) {
	 if (state_times[i] != 0) {
	    xml_out.field(state_names[i],(long) state_times[i]);
	  }
       }
      if (block_ons != 0) xml_out.field("BLOCKS",block_ons);

    }

   void updateTotals(double [] totals) {
      for (int i = 0; i < JIVE_STATE_MAX; ++i) {
	 totals[i] += state_times[i];
       }
    }

   public void updateFrom(ThreadStats ps,int idx) {
      last_time = ps.last_time;
      if (ps.last_state != JIVE_STATE_NEW) last_state = ps.last_state;
      else if (thread_table[idx].isSynced()) last_state = JIVE_STATE_SYNC;
      else last_state = JIVE_STATE_RUN;
    }

   void addBlock()				{ ++block_ons; }
   int getBlocks()				{ return block_ons; }

}	// end of subclass ThreadStats





/********************************************************************************/
/*										*/
/*	Timing trigger class							*/
/*										*/
/********************************************************************************/

private class JiveTimer extends Thread
{
   private long time_delay;

   JiveTimer() {
      super("JiveTimer");
      __jive_field__ = -1;
      String td = System.getProperty(JIVE_PROPERTY_INTERVAL);
      if (td == null) time_delay = JIVE_TIME_DELAY;
      else {
	 try {
	    time_delay = Integer.parseInt(td);
	  }
	 catch (NumberFormatException e) {
	    time_delay = JIVE_TIME_DELAY;
	  }
       }
      setDaemon(true);
    }

   public void run() {
      for ( ; ; ) {
	 try {
	    sleep(time_delay);
	  }
	 catch (InterruptedException e) {
	    break;
	  }
	 collectStatistics();
       }
    }

}	// end of subclass JiveTimer




/********************************************************************************/
/*										*/
/*	XmlWriterClass								*/
/*										*/
/********************************************************************************/


private static class XmlWriter extends PrintWriter
{
   private Writer	   base_writer;
   private Vector	   element_stack;
   private int		   open_state;
   private String	   indent_string;

   private static final int	   STATE_DONE = 0;
   private static final int	   STATE_OPEN = 1;
   private static final int	   STATE_CLOSED = 2;
   private static final int	   STATE_TEXT = 3;


   public XmlWriter(Writer w) {
      super(w);
      base_writer = w;
      element_stack = new Vector();
      indent_string = "  ";
      open_state = STATE_DONE;
    }

   void begin(String elt) {
      switch (open_state) {
	 case STATE_DONE :
	    break;
	 case STATE_OPEN :
	    println(">");
	    break;
	 case STATE_CLOSED :
	    break;
       }
      indent();
      print("<");
      print(elt);
      open_state = STATE_OPEN;
      element_stack.addElement(elt);
    }

   void end() {
      int ln = element_stack.size();
      if (ln == 0) throw new Error("End with no corresponding begin");
      String elt = (String) element_stack.lastElement();
      element_stack.setSize(ln-1);

      if (open_state == STATE_DONE) return;
      else if (open_state == STATE_OPEN) {
	 println(" />");
       }
      else if (elt != null) {
	 if (open_state != STATE_TEXT) indent();
	 print("</");
	 print(elt);
	 println(">");
       }
      if (ln == 1) open_state = STATE_DONE;
      else open_state = STATE_CLOSED;
    }

   void field(String elt,String val) {
      if (open_state != STATE_OPEN) throw new Error("Field must be specified inside an element");
      print(" ");
      print(elt);
      print("='");
      outputXmlString(val);
      print("'");
    }

   void field(String elt,boolean fg)			{ field(elt,String.valueOf(fg)); }
   void field(String elt,short v)			{ field(elt,String.valueOf(v)); }
   void field(String elt,int v) 			{ field(elt,String.valueOf(v)); }
   void field(String elt,long v)			{ field(elt,Long.toString(v)); }
   void field(String elt,double v)			{ field(elt,String.valueOf(v)); }

   void text(String t) {
      if (open_state == STATE_OPEN) {
	 print(">");
	 open_state = STATE_TEXT;
       }
      outputXmlString(t);
    }

   private void indent() {
      int ln = element_stack.size();
      for (int i = 0; i < ln; ++i) print(indent_string);
    }

   private void outputXmlString(String s) {
      if (s == null) return;
      for (int i = 0; i < s.length(); ++i) {
	 char c = s.charAt(i);
	 switch (c) {
	    case '&' :
	       print("&amp;");
	       break;
	    case '<' :
	       print("&lt;");
	       break;
	    case '>' :
	       print("&gt;");
	       break;
	    case '"' :
	       print("&quot;");
	       break;
	    case '\'' :
	       print("&apos;");
	       break;
	    default :
	       print(c);
	       break;
	  }
       }
    }

}	// end of subclass XmlWriter




/********************************************************************************/
/*										*/
/*	Class to set up a connection to JIVE					*/
/*										*/
/********************************************************************************/

private abstract static class Client {

   abstract void sendHeader();

   abstract void sendTrailer();

   abstract XmlWriter getXmlWriter();

}	// end of abstract class Client




private static class FileClient extends Client {

   XmlWriter xml_writer;

   FileClient() {
      try {
	 xml_writer = new XmlWriter(new BufferedWriter(new FileWriter("trace.out")));
       }
      catch (IOException e) {
	 System.err.println("JIVE: Problem setting up trace file: " + e.getMessage());
	 System.exit(1);
       }
    }

   void sendHeader()				{ }
   void sendTrailer()				{ xml_writer.flush(); }
   XmlWriter getXmlWriter()			{ return xml_writer; }

}	// end of abstract class FileClient



private static class MintClient extends Client {

   private PrintWriter mint_writer;

   MintClient(String fn) {
      mint_writer = null;
      while (mint_writer == null) {
	 try {
	    String id = MINT_DEFAULT_SERVICE_NAME + "_" + System.getProperty("user.name") + "_" +
	       InetAddress.getLocalHost().getHostName();
	    LineNumberReader lnr = new LineNumberReader(new FileReader(fn));
	    String hn = lnr.readLine();
	    if (hn == null) throw new IOException("Master file " + fn + " is empty");
	    StringTokenizer tok = new StringTokenizer(hn,"\t");
	    String host = tok.nextToken().trim();
	    int port = Integer.parseInt(tok.nextToken());
	    Socket master = new Socket(host,port);

	    OutputStream ots = master.getOutputStream();
	    PrintStream ps = new PrintStream(ots,true);
	    ps.println("FIND\t" + id);
	    ps.flush();

	    InputStream ins = master.getInputStream();
	    BufferedInputStream bins = new BufferedInputStream(ins);
	    InputStreamReader insr = new InputStreamReader(bins);
	    lnr = new LineNumberReader(insr);
	    String rslt = lnr.readLine();
	    if (rslt == null || !rslt.startsWith("USE\t")) throw new IOException("No server " + id + " running");
	    tok = new StringTokenizer(rslt);
	    String u = tok.nextToken();
	    String h = tok.nextToken();
	    int p = Integer.parseInt(tok.nextToken());
	    Socket cs = new Socket(h,p);

	    master.close();

	    mint_writer = new PrintWriter(new OutputStreamWriter(cs.getOutputStream()),true);
	  }
	 catch (Exception e) {
	    System.err.println("JIVE: Waiting for server connection: " + e.getMessage());
	  }
	 if (mint_writer == null) {
	    try {
	       Thread.currentThread().sleep(1);
	     }
	    catch (InterruptedException e) { }
	  }
       }
    }

   void sendHeader() {
      mint_writer.println(MINT_HEADER_SEND + " " + MINT_REPLY_NONE + " " + MINT_MSG_NO_REPLY);
    }

   void sendTrailer() {
      mint_writer.println(MINT_TRAILER);
      mint_writer.flush();
    }

   XmlWriter getXmlWriter()		{ return new XmlWriter(mint_writer); }

}	// end of subclass MintClient




private static class SocketClient extends Client {

   private PrintWriter sock_writer;

   SocketClient(String loc) {
      sock_writer = null;
      while (sock_writer == null) {
	 try {
	    StringTokenizer tok = new StringTokenizer(loc,"\t@");
	    String host = tok.nextToken().trim();
	    int port = Integer.parseInt(tok.nextToken());
	    Socket cs = new Socket(host,port);
	    sock_writer = new PrintWriter(new OutputStreamWriter(cs.getOutputStream()),true);
	  }
	 catch (Exception e) {
	    System.err.println("JIVE: Waiting for server connection: " + e.getMessage());
	  }
	 if (sock_writer == null) {
	    try {
	       Thread.currentThread().sleep(1);
	     }
	    catch (InterruptedException e) { }
	  }
       }
    }

   void sendHeader() {
    }

   void sendTrailer() {
      sock_writer.println(JIVE_TRAILER);
      sock_writer.flush();
    }

   XmlWriter getXmlWriter()		{ return new XmlWriter(sock_writer); }

}	// end of subclass MintClient




}	// end of class JiveRunTime




/* end of JiveRunTime.java */

